﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

namespace Atomic.Structures
{
	[Serializable]
	[DebuggerDisplay("{Symbol}")]
	public abstract class Atom : IEquatable<Atom>
	{
		private static List<Atom> atoms;

		static Atom()
		{
			// Standard atomic masses and electron configurations from Wikipedia:
			// http://en.wikipedia.org/w/index.php?title=List_of_elements&oldid=573002193

			H = new RealAtom(1, "H", 1.00794);
			He = new RealAtom(2, "He", 4.0026022);
			Li = new RealAtom(3, "Li", 6.94);
			Be = new RealAtom(4, "Be", 9.0121823);
			B = new RealAtom(5, "B", 10.81);
			C = new RealAtom(6, "C", 12.0107, "[He] 2s2 2p2");
			N = new RealAtom(7, "N", 14.0067, "[He] 2s2 2p3");
			O = new RealAtom(8, "O", 15.9994);
			F = new RealAtom(9, "F", 18.99840325);
			Ne = new RealAtom(10, "Ne", 20.17976);
			Na = new RealAtom(11, "Na", 22.98976928);
			Mg = new RealAtom(12, "Mg", 24.3059);
			Al = new RealAtom(13, "Al", 26.98153868);
			Si = new RealAtom(14, "Si", 28.085);
			P = new RealAtom(15, "P", 30.9737622);
			S = new RealAtom(16, "S", 32.06);
			Cl = new RealAtom(17, "Cl", 35.45);
			Ar = new RealAtom(18, "Ar", 39.9481);
			K = new RealAtom(19, "K", 39.09831);
			Ca = new RealAtom(20, "Ca", 40.0784);
			Sc = new RealAtom(21, "Sc", 44.9559126);
			Ti = new RealAtom(22, "Ti", 47.8671);
			V = new RealAtom(23, "V", 50.94151);
			Cr = new RealAtom(24, "Cr", 51.99616);
			Mn = new RealAtom(25, "Mn", 54.9380455);
			Fe = new RealAtom(26, "Fe", 55.845, "[Ar] 3d6 4s2");
			Co = new RealAtom(27, "Co", 58.9331955);
			Ni = new RealAtom(28, "Ni", 58.69344, "[Ar] 4s1 3d9");
			Cu = new RealAtom(29, "Cu", 63.5463, "[Ar] 3d10 4s1");
			Zn = new RealAtom(30, "Zn", 65.382);
			Ga = new RealAtom(31, "Ga", 69.7231);
			Ge = new RealAtom(32, "Ge", 72.6308);
			As = new RealAtom(33, "As", 74.921602);
			Se = new RealAtom(34, "Se", 78.963);
			Br = new RealAtom(35, "Br", 79.904);
			Kr = new RealAtom(36, "Kr", 83.7982);
			Rb = new RealAtom(37, "Rb", 85.46783);
			Sr = new RealAtom(38, "Sr", 87.621);
			Y = new RealAtom(39, "Y", 88.905852);
			Zr = new RealAtom(40, "Zr", 91.2242);
			Nb = new RealAtom(41, "Nb", 92.906382);
			Mo = new RealAtom(42, "Mo", 95.962);
			Tc = new RealAtom(43, "Tc", 98.0);
			Ru = new RealAtom(44, "Ru", 101.072);
			Rh = new RealAtom(45, "Rh", 102.905502);
			Pd = new RealAtom(46, "Pd", 106.421);
			Ag = new RealAtom(47, "Ag", 107.86822);
			Cd = new RealAtom(48, "Cd", 112.4118);
			In = new RealAtom(49, "In", 114.8181);
			Sn = new RealAtom(50, "Sn", 118.7107);
			Sb = new RealAtom(51, "Sb", 121.7601);
			Te = new RealAtom(52, "Te", 127.603);
			I = new RealAtom(53, "I", 126.904473);
			Xe = new RealAtom(54, "Xe", 131.2936);
			Cs = new RealAtom(55, "Cs", 132.9054519);
			Ba = new RealAtom(56, "Ba", 137.3277);
			La = new RealAtom(57, "La", 138.905477);
			Ce = new RealAtom(58, "Ce", 140.1161);
			Pr = new RealAtom(59, "Pr", 140.907652);
			Nd = new RealAtom(60, "Nd", 144.2423);
			Pm = new RealAtom(61, "Pm", 145.0);
			Sm = new RealAtom(62, "Sm", 150.362);
			Eu = new RealAtom(63, "Eu", 151.9641);
			Gd = new RealAtom(64, "Gd", 157.253);
			Tb = new RealAtom(65, "Tb", 158.925352);
			Dy = new RealAtom(66, "Dy", 162.5001);
			Ho = new RealAtom(67, "Ho", 164.930322);
			Er = new RealAtom(68, "Er", 167.2593);
			Tm = new RealAtom(69, "Tm", 168.934212);
			Yb = new RealAtom(70, "Yb", 173.0545);
			Lu = new RealAtom(71, "Lu", 174.96681);
			Hf = new RealAtom(72, "Hf", 178.492);
			Ta = new RealAtom(73, "Ta", 180.947882);
			W = new RealAtom(74, "W", 183.841);
			Re = new RealAtom(75, "Re", 186.2071);
			Os = new RealAtom(76, "Os", 190.233);
			Ir = new RealAtom(77, "Ir", 192.2173);
			Pt = new RealAtom(78, "Pt", 195.0849);
			Au = new RealAtom(79, "Au", 196.9665694, "[Xe] 4f14 5d10 6s1");
			Hg = new RealAtom(80, "Hg", 200.5923);
			Tl = new RealAtom(81, "Tl", 204.389);
			Pb = new RealAtom(82, "Pb", 207.21);
			Bi = new RealAtom(83, "Bi", 208.980401);
			Po = new RealAtom(84, "Po", 209.0);
			At = new RealAtom(85, "At", 210.0);
			Rn = new RealAtom(86, "Rn", 222.0);
			Fr = new RealAtom(87, "Fr", 223.0);
			Ra = new RealAtom(88, "Ra", 226.0);
			Ac = new RealAtom(89, "Ac", 227.0);
			Th = new RealAtom(90, "Th", 232.038062);
			Pa = new RealAtom(91, "Pa", 231.035882);
			U = new RealAtom(92, "U", 238.028913);
			Np = new RealAtom(93, "Np", 237.0);
			Pu = new RealAtom(94, "Pu", 244.0);
			Am = new RealAtom(95, "Am", 243.0);
			Cm = new RealAtom(96, "Cm", 247.0);
			Bk = new RealAtom(97, "Bk", 247.0);
			Cf = new RealAtom(98, "Cf", 251.0);
			Es = new RealAtom(99, "Es", 252.0);
			Fm = new RealAtom(100, "Fm", 257.0);
			Md = new RealAtom(101, "Md", 258.0);
			No = new RealAtom(102, "No", 259.0);
			Lr = new RealAtom(103, "Lr", 262.0);
			Rf = new RealAtom(104, "Rf", 267.0);
			Db = new RealAtom(105, "Db", 268.0);
			Sg = new RealAtom(106, "Sg", 269.0);
			Bh = new RealAtom(107, "Bh", 270.0);
			Hs = new RealAtom(108, "Hs", 269.0);
			Mt = new RealAtom(109, "Mt", 278.0);
			Ds = new RealAtom(110, "Ds", 281.0);
			Rg = new RealAtom(111, "Rg", 281.0);
			Cn = new RealAtom(112, "Cn", 285.0);
			Uut = new RealAtom(113, "Uut", 286.0);
			Fl = new RealAtom(114, "Fl", 289.0);
			Uup = new RealAtom(115, "Uup", 288.0);
			Lv = new RealAtom(116, "Lv", 293.0);
			Uus = new RealAtom(117, "Uus", 294.0);
			Uuo = new RealAtom(118, "Uuo", 294.0);
			Vacancy = new VacancyAtom();
			Undefined = new UndefinedAtom();

			atoms = new List<Atom>(new Atom[] { H, He, Li, Be, B, C, N, O, F, Ne, Na, Mg, Al, Si, P, S, Cl, Ar, K, Ca, Sc, Ti, V, Cr, Mn, Fe, Co, Ni, Cu, Zn, Ga, Ge, As, Se, Br, Kr, Rb, Sr, Y, Zr, Nb, Mo, Tc, Ru, Rh, Pd, Ag, Cd, In, Sn, Sb, Te, I, Xe, Cs, Ba, La, Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb, Lu, Hf, Ta, W, Re, Os, Ir, Pt, Au, Hg, Tl, Pb, Bi, Po, At, Rn, Fr, Ra, Ac, Th, Pa, U, Np, Pu, Am, Cm, Bk, Cf, Es, Fm, Md, No, Lr, Rf, Db, Sg, Bh, Hs, Mt, Ds, Rg, Cn, Uut, Fl, Uup, Lv, Uus, Uuo });
		}

		/// <summary>
		/// Creates a new <see cref="Atom" /> instance representing spin-polarization. Automatically recognized by VASP in the initial configuration (MAGMOM tag).
		/// </summary>
		public Atom SpinPolarize(double polarization)
		{
			if (polarization == 0.0)
			{
				return this;
			}

			return new SpinPolarizedAtom(this, polarization); // FIXME polarization = magnetic moment (or a g-factor different - which one is used in vasp?)
		}

		/// <summary>
		/// Creates a spin-unpolarized copy of the atom.
		/// </summary>
		public virtual Atom SpinUnpolarize()
		{
			return this;
		}

		public abstract bool Equals(Atom other);

		public override bool Equals(object other)
		{
			return Equals(other as Atom);
		}

		public override int GetHashCode()
		{
			// Just an arbitrary non-trivial hashcode.
			return Number.GetHashCode();
		}

		public static Atom Parse(int number)
		{
			Atom atom = atoms.Find(a => a.Number == number);
			if (atom == null)
			{
				throw new ArgumentException("Atom not found.");
			}

			return atom;
		}

		public static Atom Parse(string symbol)
		{
			if (symbol.Split('+', '-').Length == 2)
			{
				// FIXME
				int sign = symbol.Contains("+") ? 1 : -1;
				double m = sign * double.Parse(symbol.Split('+', '-')[1], CultureInfo.InvariantCulture);
				return new SpinPolarizedAtom(Parse(symbol.Split('+', '-')[0]), m);
			}

			if (symbol == "Vac" || symbol == "Va")
			{
				return Vacancy;
			}

			Atom atom = atoms.Find(a => a.Symbol == symbol);
			if (atom == null)
			{
				throw new ArgumentException("Atom not found.");
			}

			return atom;
		}

		public static Atom Parse(string symbol, bool allowPlaceholder)
		{
			if (allowPlaceholder && symbol.Contains(","))
			{
				return PlaceholderAtom.ParsePlaceholder(symbol);
			}

			return Parse(symbol);
		}

		public static PlaceholderAtom Placeholder(params Atom[] atoms)
		{
			return new PlaceholderAtom(atoms);
		}

		/*public static Composition operator *(int factor, Atom atom)
		{
			return new Composition(atom, factor);
		}

		public static Composition operator +(Atom atom1, Atom atom2)
		{
			return new Composition(new Composition(atom1), new Composition(atom2));
		}

		public static CompositionConstraint operator <=(Atom atom, int constraint)
		{
			return new CompositionConstraint(new Composition(atom), constraint);
		}

		public static CompositionConstraint operator >=(Atom atom, int constraint)
		{
			throw new NotImplementedException();
		}*/

		/// <summary>
		/// The atomic number of this atom.
		/// </summary>
		public abstract int Number
		{
			get;
		}

		/// <summary>
		/// The atomic symbol of this atom.
		/// </summary>
		public abstract string Symbol
		{
			get;
		}

		/// <summary>
		/// Average isotopic mass of this atom.
		/// </summary>
		public abstract double Mass
		{
			get;
		}

		public abstract string ElectronConfiguration
		{
			get;
		}

		/// <summary>
		/// Represents hydrogen.
		/// </summary>
		public static Atom H
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents helium.
		/// </summary>
		public static Atom He
		{
			get;
			private set;
		}

		public static Atom Li
		{
			get;
			private set;
		}

		public static Atom Be
		{
			get;
			private set;
		}

		public static Atom B
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents carbon.
		/// </summary>
		public static Atom C
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents nitrogen.
		/// </summary>
		public static Atom N
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents oxygen.
		/// </summary>
		public static Atom O
		{
			get;
			private set;
		}

		public static Atom F
		{
			get;
			private set;
		}

		public static Atom Ne
		{
			get;
			private set;
		}

		public static Atom Na
		{
			get;
			private set;
		}

		public static Atom Mg
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents aluminium.
		/// </summary>
		public static Atom Al
		{
			get;
			private set;
		}

		public static Atom Si
		{
			get;
			private set;
		}

		public static Atom P
		{
			get;
			private set;
		}

		public static Atom S
		{
			get;
			private set;
		}

		public static Atom Cl
		{
			get;
			private set;
		}

		public static Atom Ar
		{
			get;
			private set;
		}

		public static Atom K
		{
			get;
			private set;
		}

		public static Atom Ca
		{
			get;
			private set;
		}

		public static Atom Sc
		{
			get;
			private set;
		}

		public static Atom Ti
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents vanadium.
		/// </summary>
		public static Atom V
		{
			get;
			private set;
		}

		public static Atom Cr
		{
			get;
			private set;
		}

		public static Atom Mn
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents iron.
		/// </summary>
		public static Atom Fe
		{
			get;
			private set;
		}

		public static Atom Co
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents nickel.
		/// </summary>
		public static Atom Ni
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents copper.
		/// </summary>
		public static Atom Cu
		{
			get;
			private set;
		}

		public static Atom Zn
		{
			get;
			private set;
		}

		public static Atom Ga
		{
			get;
			private set;
		}

		public static Atom Ge
		{
			get;
			private set;
		}

		public static Atom As
		{
			get;
			private set;
		}

		public static Atom Se
		{
			get;
			private set;
		}

		public static Atom Br
		{
			get;
			private set;
		}

		public static Atom Kr
		{
			get;
			private set;
		}

		public static Atom Rb
		{
			get;
			private set;
		}

		public static Atom Sr
		{
			get;
			private set;
		}

		public static Atom Y
		{
			get;
			private set;
		}

		public static Atom Zr
		{
			get;
			private set;
		}

		public static Atom Nb
		{
			get;
			private set;
		}

		public static Atom Mo
		{
			get;
			private set;
		}

		public static Atom Tc
		{
			get;
			private set;
		}

		public static Atom Ru
		{
			get;
			private set;
		}

		public static Atom Rh
		{
			get;
			private set;
		}

		public static Atom Pd
		{
			get;
			private set;
		}

		public static Atom Ag
		{
			get;
			private set;
		}

		public static Atom Cd
		{
			get;
			private set;
		}

		public static Atom In
		{
			get;
			private set;
		}

		public static Atom Sn
		{
			get;
			private set;
		}

		public static Atom Sb
		{
			get;
			private set;
		}

		public static Atom Te
		{
			get;
			private set;
		}

		public static Atom I
		{
			get;
			private set;
		}

		public static Atom Xe
		{
			get;
			private set;
		}

		public static Atom Cs
		{
			get;
			private set;
		}

		public static Atom Ba
		{
			get;
			private set;
		}

		public static Atom La
		{
			get;
			private set;
		}

		public static Atom Ce
		{
			get;
			private set;
		}

		public static Atom Pr
		{
			get;
			private set;
		}

		public static Atom Nd
		{
			get;
			private set;
		}

		public static Atom Pm
		{
			get;
			private set;
		}

		public static Atom Sm
		{
			get;
			private set;
		}

		public static Atom Eu
		{
			get;
			private set;
		}

		public static Atom Gd
		{
			get;
			private set;
		}

		public static Atom Tb
		{
			get;
			private set;
		}

		public static Atom Dy
		{
			get;
			private set;
		}

		public static Atom Ho
		{
			get;
			private set;
		}

		public static Atom Er
		{
			get;
			private set;
		}

		public static Atom Tm
		{
			get;
			private set;
		}

		public static Atom Yb
		{
			get;
			private set;
		}

		public static Atom Lu
		{
			get;
			private set;
		}

		public static Atom Hf
		{
			get;
			private set;
		}

		public static Atom Ta
		{
			get;
			private set;
		}

		public static Atom W
		{
			get;
			private set;
		}

		public static Atom Re
		{
			get;
			private set;
		}

		public static Atom Os
		{
			get;
			private set;
		}

		public static Atom Ir
		{
			get;
			private set;
		}

		public static Atom Pt
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents gold.
		/// </summary>
		public static Atom Au
		{
			get;
			private set;
		}

		public static Atom Hg
		{
			get;
			private set;
		}

		public static Atom Tl
		{
			get;
			private set;
		}

		public static Atom Pb
		{
			get;
			private set;
		}

		public static Atom Bi
		{
			get;
			private set;
		}

		public static Atom Po
		{
			get;
			private set;
		}

		public static Atom At
		{
			get;
			private set;
		}

		public static Atom Rn
		{
			get;
			private set;
		}

		public static Atom Fr
		{
			get;
			private set;
		}

		public static Atom Ra
		{
			get;
			private set;
		}

		public static Atom Ac
		{
			get;
			private set;
		}

		public static Atom Th
		{
			get;
			private set;
		}

		public static Atom Pa
		{
			get;
			private set;
		}

		public static Atom U
		{
			get;
			private set;
		}

		public static Atom Np
		{
			get;
			private set;
		}

		public static Atom Pu
		{
			get;
			private set;
		}

		public static Atom Am
		{
			get;
			private set;
		}

		public static Atom Cm
		{
			get;
			private set;
		}

		public static Atom Bk
		{
			get;
			private set;
		}

		public static Atom Cf
		{
			get;
			private set;
		}

		public static Atom Es
		{
			get;
			private set;
		}

		public static Atom Fm
		{
			get;
			private set;
		}

		public static Atom Md
		{
			get;
			private set;
		}

		public static Atom No
		{
			get;
			private set;
		}

		public static Atom Lr
		{
			get;
			private set;
		}

		public static Atom Rf
		{
			get;
			private set;
		}

		public static Atom Db
		{
			get;
			private set;
		}

		public static Atom Sg
		{
			get;
			private set;
		}

		public static Atom Bh
		{
			get;
			private set;
		}

		public static Atom Hs
		{
			get;
			private set;
		}

		public static Atom Mt
		{
			get;
			private set;
		}

		public static Atom Ds
		{
			get;
			private set;
		}

		public static Atom Rg
		{
			get;
			private set;
		}

		public static Atom Cn
		{
			get;
			private set;
		}

		public static Atom Uut
		{
			get;
			private set;
		}

		public static Atom Fl
		{
			get;
			private set;
		}

		public static Atom Uup
		{
			get;
			private set;
		}

		public static Atom Lv
		{
			get;
			private set;
		}

		public static Atom Uus
		{
			get;
			private set;
		}

		public static Atom Uuo
		{
			get;
			private set;
		}

		/// <summary>
		/// Represents a vacant atom site.
		/// </summary>
		public static VacancyAtom Vacancy
		{
			get;
			private set;
		}

		/// <summary>
		/// An undefined atom for testing purposes.
		/// </summary>
		public static UndefinedAtom Undefined
		{
			get;
			private set;
		}

		public static bool operator ==(Atom atom1, Atom atom2)
		{
			if (object.ReferenceEquals(atom1, null))
			{
				if (object.ReferenceEquals(atom2, null))
				{
					return true;
				}

				return false;
			}

			return atom1.Equals(atom2);
		}

		public static bool operator !=(Atom atom1, Atom atom2)
		{
			return !(atom1 == atom2);
		}

		[Serializable]
		[DebuggerDisplay("{Symbol}")]
		private class RealAtom : Atom
		{
			private int number;
			private string symbol, electronConfiguration;
			private double mass;

			public RealAtom(int number, string symbol, double mass)
				: this(number, symbol, mass, null)
			{
			}
		
			public RealAtom(int number, string symbol, double mass, string electronConfiguration)
			{
				this.number = number;
				this.symbol = symbol;
				this.mass = mass;
				this.electronConfiguration = electronConfiguration;
			}

			public override bool Equals(Atom other)
			{
				if (object.ReferenceEquals(other, null))
				{
					return false;
				}

				if (object.ReferenceEquals(this, other))
				{
					return true;
				}

				if (GetType() != other.GetType())
				{
					return false;
				}

				return number == other.Number;
			}

			public override int Number
			{
				get
				{
					return number;
				}
			}

			public override string Symbol
			{
				get
				{
					return symbol;
				}
			}

			public override double Mass
			{
				get
				{
					return mass;
				}
			}

			public override string ElectronConfiguration
			{
				get
				{
					return electronConfiguration;
				}
			}
		}
	}
}
